Univerisdad de los Andes
Realizado por: Yeimy A. Cano M.
“Al entregar la solución de este parcial, yo, Yeimy A Cano con código 202213304 me comprometo a no conversar durante el desarrollo de este examen con ninguna persona que no sea el profesor del curso, sobre aspectos relacionados con el parcial; tampoco utilizaré algún medio de comunicación por voz, texto o intercambio de archivos, para consultar o compartir con otros, información sobre el tema del parcial. Soy consciente y acepto las consecuencias que acarreará para mi desempeño académico cometer fraude en este parcial”.
Primero se procede a hacer la importación de librerías para poder trabajar con los datos y poder realizar el análisis.
!pip install --upgrade pandas-profiling
!pip install pylev
import numpy as np
import pandas as pd
from pandas_profiling import ProfileReport
import seaborn as sns
from scipy import stats
import scipy
import statsmodels.api as sm
#Entrenamiento del modelo
from sklearn.linear_model import LinearRegression, SGDRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score, accuracy_score
from sklearn.preprocessing import StandardScaler, PolynomialFeatures , OneHotEncoder, MinMaxScaler
from sklearn.linear_model import LinearRegression,Lasso, Ridge
from sklearn.base import BaseEstimator,TransformerMixin
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import matplotlib as mpl
import seaborn as sns
%matplotlib inline
plt.rcParams['figure.dpi'] = 110
import pylev
import itertools
!python --version
print('NumPy', np.__version__)
print('pandas', pd.__version__)
print('SciPy', scipy.__version__)
print('statsmodels', sm.__version__)
print('Matplotlib', mpl.__version__)
print('Seaborn', sns.__version__)
Inmobiliaria Los Alpes es una inmobiliaria con varios años de trayectoria la cual se especializa en el arrendamiento de inmuebles de corta estancia. La empresa ha ido creciendo poco a poco y en la actualidad enfrenta una serie de retos relacionados con la gran cantidad de competencia que hay en el sector, dificultando la obtención de nuevos inmuebles para su administración como arrendador, así como conseguir los arrendatarios idóneos para el uso de los mismos.
Como estrategia para mantener su competitividad en el mercado, la Inmobiliaria Los Alpes ha decidido enfocarse en determinar los inmuebles que podrían llegar a ser populares desde antes de su publicación. La popularidad de un inmueble está dada en términos de la cantidad de comentarios (reviews) que ha obtenido independientemente de su calificación. Esta actividad es importante dado que los inmuebles más populares son los que generan mayor rotación, mientras que para los de menor popularidad hay que hacer una mayor inversión en marketing para promocionarlos.
La inmobiliaria busca contratar con usted un proyecto de consultoría que tiene como objetivo validar el uso de técnicas de machine learning que apoyen la toma de decisiones del equipo de marketing respecto a los inmuebles para los que deben hacer inversiones en promoción dada su baja popularidad. Le han compartido dos conjuntos de datos: (1) el histórico de sus inmuebles con su respectiva popularidad y (2) los inmuebles que están próximos a publicarse. Adicionalmente, le han compartido el diccionario de datos correspondiente.
El archivo de datos está como archivo de CSV, se hace una exploración con una muestra de datos "losalpes_history.csv"
Para trabajar con los datos, éstos se ubican en una ruta de github.
# Loading data historico
url = 'https://raw.githubusercontent.com/yacanom/CDA_MINE-4101_repo/main/Parcial%201/losalpes_history.csv'
data = pd.read_csv(url, sep=',')
# Loading data losalpes_new.csv
url2 = 'https://raw.githubusercontent.com/yacanom/CDA_MINE-4101_repo/main/Parcial%201/losalpes_new.csv'
dataP = pd.read_csv(url2, sep=',')
De acuerdo a la información entregada, el set de datos cuentan con los siguientes columnas con la respectiva descripción:
| Field | Description |
|---|---|
| id | Identificador del inmueble |
| neighbourhood group | Localidad o distrito en el que se encuentra el inmueble |
| neighbourhood | Barrio en el que se encuentra el inmueble |
| lat, long | Geolocalización del inmueble |
| country | Pais en el que se encuentra el inmueble |
| instant bookable | Indicador de si es posible realizar reserva directamente en la plataforma |
| cancellation_policy | Política de cancelacion de la reserva |
| room type | Tipo de inmueble |
| construction year | Año de construcción del inmueble |
| price | Precio por noche del inmueble |
| service fee | Costo del servicio el cual debe ser cancelado al dejar el inmueble |
| minimum nights | Cantidad mínima de noches que el inmueble puede ser reservado |
| availability 365 | Disponibilidad total en dias durante el ultimo año |
| number of reviews | Total de comentarios del inmueble |
| review rate number | Calificación promedio dada al inmueble |
data.head()
Vamos a hacer una exploración sobre el set de datos históricos, con data.info se puede hacer una ojeada muy superficial para conocer tipos de datos, cantidad, sin tener mucha información detallada que entienda el modelo.
data.shape
data.info()
De esta revisión se puede observa que hay datos faltantes, de acuerdo al tamaño del dataframe deberían haber 102083 registros pero solo el id, presenta este numero completo, los demás presentan datos pendientes.
data.describe()
Ahora usamos ProfileReport para poder hacer la exploración sobre el data set de entrenamiento (train), para no dar la información del dataset completo.
ProfileReport(data)
De acuerdo a la exploración realizada con el reporte, se puede ver que:
Revisión de los datos duplicados:
data[data.duplicated()]
Se identificaron 536 datos duplicados.
Ahora al querer revisar el listado de distritos mas populares:
df_top10 = data[{"neighbourhood group", "neighbourhood"}].groupby("neighbourhood group").count().sort_values("neighbourhood", ascending=False).rename(columns={"neighbourhood":"Total canciones"}).iloc[0:10].reset_index()
df_top10
data["neighbourhood group"].unique()
Se evidencia que hay casos de registros en donde quedó mal registrado el districto como en el caso de Quens, que debería ser "Queens".
#Se revisan las coordenadas de los distritos para ver si de esta forma se podrían imputar los distritos faltantes a paritr de las coordenadas
data[data['neighbourhood group'] == 'Manhattan']['lat'].min()
print(f"Manhattan Latitud min y max:%s", data[data['neighbourhood group'] == 'Manhattan']['lat'].min(), data[data['neighbourhood group'] == 'Manhattan']['lat'].max())
print(f"Manhattan Longitud min y max:%s", data[data['neighbourhood group'] == 'Manhattan']['long'].min(), data[data['neighbourhood group'] == 'Manhattan']['long'].max())
print(f"Queens Latitud min y max:%s", data[data['neighbourhood group'] == 'Queens']['lat'].min(), data[data['neighbourhood group'] == 'Queens']['lat'].max())
print(f"Queens Longitud min y max:%s", data[data['neighbourhood group'] == 'Queens']['long'].min(), data[data['neighbourhood group'] == 'Queens']['long'].max())
print(f"Bronx Latitud min y max:%s", data[data['neighbourhood group'] == 'Bronx']['lat'].min(), data[data['neighbourhood group'] == 'Bronx']['lat'].max())
print(f"Bronx Longitud min y max:%s", data[data['neighbourhood group'] == 'Bronx']['long'].min(), data[data['neighbourhood group'] == 'Bronx']['long'].max())
print(f"Staten Island Latitud min y max:%s", data[data['neighbourhood group'] == 'Staten Island']['lat'].min(), data[data['neighbourhood group'] == 'Staten Island']['lat'].max())
print(f"Staten Island Longitud min y max:%s", data[data['neighbourhood group'] == 'Staten Island']['long'].min(), data[data['neighbourhood group'] == 'Staten Island']['long'].max())
data[data['neighbourhood group'].isnull() & data['lat'].isnull() ]
Hay 591 casos donde el neighbourhood group no tiene datos, pero tampoco se tienen los valores en las demás columnas, de los cuales no se justifica imputarles valores.
Ahora el top 10 entre los 224 barrios:
df_top10 = data[{"neighbourhood group", "neighbourhood"}].groupby("neighbourhood").count().sort_values("neighbourhood group", ascending=False).rename(columns={"neighbourhood group":"Total sitios"}).iloc[0:10].reset_index()
df_top10
data.loc[:, 'neighbourhood'].sort_values().unique()
Se ven algunos problemas de datos como: "'Bay Terrace, Staten Island'" donde se podría interpretar que el barrio es "Bay Terrace" del distrito de Staten Island, sucede lo mismo con "Chelsea, Staten Island".
data[(data['neighbourhood'] == "Bay Terrace, Staten Island") | (data['neighbourhood'] == "Chelsea, Staten Island")]
Se encuentran 4 registros que presentan esa novedad, como son casos puntuales podrían corregirse puntualmente.
Ahora se revisa, si los datos nulos de barrio son diferentes a los vistos con nulos en el distrito:
data[data['neighbourhood'].isnull() & ~data['neighbourhood group'].isnull() ]
Luego los datos nulos de barrio estarían dentro dos 591 registros completamente sin datos identificados anteriormente. Y quedarían 16 sin datos de barrio.
sns.histplot(data=data, x="lat")
sns.boxplot(x=data["lat"])
Del histograma ybloxplot se puede ver que hay un amplio número de datos de latitud que se concentran en el medio, con algunos valores que se escapan del Q1 y Q3.
sns.histplot(data=data, x="long")
sns.boxplot(x=data["long"])
data.loc[:, {'lat', 'long'}].describe()
#revisando los valores vacíos de lat y long, que no esten en los antes evaluados:
data[ data['lat'].isnull() & data['long'].isnull() & ~data['neighbourhood'].isnull() ]
data[ data['lat'].isnull() & data['long'].isnull() & ~data['neighbourhood'].isnull() ].count()
Al revisar los datos nulos tanto para long y lat, que contengan datos de las otras varibles, solo se encuentran 8 casos. Es decir que los otros datos nulos estarían dentro de los 591 registros que no tienen valores en las diferentes columas. Aun sin modelar, se podría pensar como hipotesis que las coordenadas geográficas tendrian mas efecto que el barrio o el distrito, por lo que para estos casos dado que lat y long no tienen una varianza muy alta pueden reemplazarse los valores vacios con la media de cada uno.
data['lat'] = data['lat'].fillna(data['lat'].median())
data[ data['long'].isnull() & ~data['neighbourhood'].isnull() ]
sns.histplot(data=data, x="cancellation_policy", stat="percent")
Respecto a la política de cancelación, se observa que los tres valores esta bien distribuida la data.
#revisando los valores vacíos de room type, que no esten en los antes evaluados:
data[ data['room type'].isnull() & ~data['neighbourhood'].isnull() ]
No se evidencian datosa vacios para "room type" diferente a los 591 registros reportados anteriormente.
Ahora se visualiza un boxplot, de la columna precio:
dfprice = data.copy()
dfprice['price'] = dfprice['price'].str.replace(r'$','')
dfprice['price'] = dfprice['price'].str.replace(',','.')
dfprice['price'] = dfprice['price'].str.replace(r'\t','')
dfprice['price'] = dfprice['price'].astype(float)
boxplot = dfprice.boxplot(column=['price'])
dfprice['service fee'] = dfprice['service fee'].str.replace(r'$','')
dfprice['service fee'] = dfprice['service fee'].str.replace(r',','.')
dfprice['service fee'] = dfprice['service fee'].str.replace(r'\t','')
dfprice['service fee'] = dfprice['service fee'].astype(float)
boxplot = dfprice.boxplot(column=['service fee'])
Se observa que si se manejara price y serve fee con números hay valores ourliers para ambos, viendo un poco en detalle:
dfprice.loc[:, ['price', 'service fee']].describe()
dfprice.loc[:, ['price', 'service fee']].quantile(.95)
Ambos presentan valores atípicos como es el caso de los negativos, y de acuerdo al 95 percentil de los datos vemos valores extremos de acuerdo al valor máximo.
data[ data['number of reviews'].isnull() & ~data['review rate number'].isnull() ]
data[ ~data['number of reviews'].isnull() & data['review rate number'].isnull() ]
dfprice['review rate number'].describe()
dfprice['review rate number'].median()
sns.boxplot(x=dfprice["number of reviews"])
dfprice["number of reviews"].describe()
De acuerdo al entendimiento de los datos realizados se crea la función la función preprocess_quality que enmarca las diferentes acciones a realizar al dataframe de datos.
1) como se encontraron 536 de 102083 registros como duplicados, serán borrados éstos registros
2) Corregir el nombre de neighbourhood group, con la función fix_neighbourhood_group usando el método levenshtein. Para el caso de los valores faltantes, aplicar buscando por las coordenadas a qué distrito pertenece o si tiene el nombre del barrio, asignar el distrito que le corresponde. Si hay registros que no cumplan alguna de las anteriores el registro se borra.
3) En el caso de neighbourhood como la mayoría de registros nulos se borra en el punto 1, los pocos que quedan se les asigna como barrio la moda para esta columna (Bedford-Stuyvesant).
4) Como se evidenció que la mayoría de nan de lat y long estan en los registros mencionados en el punto 1, para los que si tienen datos en otras columnasse propone imputar el valor con la mediana de cada uno, dado que son variables con poca desviación y que no son más de 20 registros.
5) Para el caso de Country, todos los paises quedaran como "United States".
6) En el caso de instant_bookable que es de tipo boleano False tiene el 50.1% mientras que True el 49.8% (cuando se quitan los registros indicados en 1), dado que los faltantes son el 0.1% se decide colocar los valores faltantes el false.
7) La columna cancellation_policy tiene un 0.1% de valores faltantes, es variable categórica de 3 posibles valores para los cuales se tienen: moderate 33.5%, strict 33.2% y flexible 33.2%. Se decide usar la moda para reemplazar los valores faltantes.
8) La columna construction year presenta valores negativos, solo 3, y pareciera fue un error de digitación: 1020 y 1022, se actualiza los valores a 2020 y 2022. (sumando 1000). Para los valores faltantes se usará la mediana, ya que la desviación esde 7.
9) Remover el signo $ de las columnas price y service fee, y que sean valores numéricos, reemplazar los numeros negativos y valores faltantes con la mediana. En ambos casos se identificaron outliers muy superiores al percentil 95, por lo que se ajustan los valores superiores a este un valor ligeramente superior.
10) Para el caso de minimum nights que tiene valores negativos, se observa que la mediana es 3 y el Q3 es 5, por lo que se dejará el valor de la mediana.
11) En el caso de availability 365 tiene valroes superiores a 365 que se espera que sea el máximo, los valores superiores a 365 se igualan a éste valor. Por otra parte para los valores negativos y ausentes se usará la media para imputarlos.
12) En la columna number of reviews, dado que es la varaible objetivo se prefiere borrar en vez de imputar por algun valor que realmente pueda no reflejar la popularidad del sitio.
13) En el caso review rate number se sugiere reemplazar los valores faltantes con la mediana, que de aceurdo a lo visto es un valor de 3.
def fix_neighbourhood_group(neighbourhood_group):
if pylev.levenshtein('Manhattan', neighbourhood_group) <= 2:
return 'Manhattan'
elif pylev.levenshtein('Brooklyn', neighbourhood_group) <= 2:
return 'Brooklyn'
elif pylev.levenshtein('Queens', neighbourhood_group) <= 2:
return 'Queens'
elif pylev.levenshtein('Bronx', neighbourhood_group) <= 2:
return 'Bronx'
elif pylev.levenshtein('Staten Island', neighbourhood_group) <= 2:
return 'Staten Island'
def preprocess(df):
#Eliminar los datos duplicados:
df = df.drop_duplicates()
#Dado los 591 que evidenciaron que solo tenia valores para el índice, se borran aquellos que no tienen valores en el distrito ni en la geolocalización
df= df.drop(df[df['neighbourhood group'].isnull() & df['lat'].isnull() & df['long'].isnull() ].index)
#Asignar distrito a partir de los datos de Geolocalización en la columna neighbourhood group, los rangos se tomaron de los min y max de lat y long de los que si tenian datos
df.loc[ (df['lat'] >= 40.70234) & (df['lat'] <= 40.87821) & (df['long'] >= -74.01851) & (df['long'] <= -73.90855), ['neighbourhood group'] ] = 'Manhattan'
df.loc[ (df['lat'] >= 40.56546) & (df['lat'] <= 40.79721) & (df['long'] >= -73.95953) & (df['long'] <= -73.70522), ['neighbourhood group'] ] = 'Queens'
df.loc[ (df['lat'] >= 40.80011) & (df['lat'] <= 40.91697) & (df['long'] >= -73.93296) & (df['long'] <= -73.78158), ['neighbourhood group'] ] = 'Bronx'
df.loc[ (df['lat'] >= 40.49979) & (df['lat'] <= 40.64816) & (df['long'] >= -74.24984) & (df['long'] <= -74.06092), ['neighbourhood group'] ] = 'Staten Island'
#Generar un diccionario con clave barrio y de valor distrito, como otra forma para colocar el nombre del distrito a partir del barrio
distritos = df.loc[~df['neighbourhood group'].isnull(), ['neighbourhood', 'neighbourhood group']]
d = dict([(i,a) for i,a in zip(distritos['neighbourhood'], distritos['neighbourhood group'])])
df.loc[ (df['neighbourhood group'].isnull() ) , ['neighbourhood group'] ] = df["neighbourhood"].apply(lambda x: d[x])
#Eliminar los registros filas con datos faltantes de distrito (si no quedo corregido en alguno de los pasos anteriores):
df = df.dropna(subset=['neighbourhood group'])
#Corregir el nombre de neighbourhood group con Levenshtein método.
df['neighbourhood group'] = df['neighbourhood group'].apply(fix_neighbourhood_group)
#Correccion de los dos barrios "Bay Terrace, Staten Island" "Chelsea, Staten Island"
df['neighbourhood'] = df['neighbourhood'].apply(lambda x: "Bay Terrace" if (x == "Bay Terrace, Staten Island") else x)
df['neighbourhood'] = df['neighbourhood'].apply(lambda x: "Chelsea" if (x == "Chelsea, Staten Island") else x)
#Imputar los valores pendientes de barrio con la moda:
df['neighbourhood'] = df['neighbourhood'].fillna(pd.Series(df["neighbourhood"].values.flatten()).mode()[0])
#Asignar los valores de la media en lat y long para los valores vacíos:
df['lat'] = df['lat'].fillna(df['lat'].median())
df['long'] = df['long'].fillna(df['long'].median())
#Ajustar la columna country:
df['country'] = "United States"
#Asignar False en instant_bookable para los valores faltantes
df['instant_bookable'] = df['instant_bookable'].fillna(pd.Series(df["instant_bookable"].values.flatten()).mode()[0])
#Asignar False en instant_bookable para los valores faltantes
df['cancellation_policy'] = df['cancellation_policy'].fillna(pd.Series(df["cancellation_policy"].values.flatten()).mode()[0])
#construction year: tiene valores de los años 1022 y 1021
df['construction year'] = df['construction year'].apply(lambda x: x+1000 if (x<=1022) else x)
df['construction year'] = df['construction year'].fillna(df['construction year'].median())
#remover el signo $ de las columnas price y service fee:
df['price'] = df['price'].str.replace(r'$','')
df['price'] = df['price'].str.replace(',','.')
df['price'] = df['price'].str.replace(r'\t','')
df['price'] = df['price'].astype(float)
df['service fee'] = df['service fee'].str.replace(r'$','')
df['service fee'] = df['service fee'].str.replace(',','.')
df['service fee'] = df['service fee'].str.replace(r'\t','')
df['service fee'] = df['service fee'].astype(float)
#Reemplazar los valores faltantes de: price, service fee con el valor medio
df['price'] = df['price'].fillna(df['price'].median())
df['service fee'] = df['service fee'].fillna(df['service fee'].median())
#valores negativos de price y service fee
df['price'] = df['price'].apply(lambda x: df['price'].median() if (x<0) else x)
df['service fee'] = df['service fee'].apply(lambda x: df['service fee'].median() if (x<0) else x)
#Modificar valores ousider de price: 941 es el 95 percentil, service fee 229.0 es el p95
df['price'] = df['price'].apply(lambda x: 950 if (x>950) else x)
df['service fee'] = df['service fee'].apply(lambda x: 250 if (x>250) else x)
#valores negativos de minimum nights usa 3 que es el de la medina, y lo mismo para los valores faltantes:
df['minimum nights'] = df['minimum nights'].apply(lambda x: df['minimum nights'].median() if (x<0) else x)
df['minimum nights'] = df['minimum nights'].fillna(df['minimum nights'].median())
#availability 365 valores superiores a 365 se deja con el valor máximo: 365
df['availability 365'] = df['availability 365'].apply(lambda x: 365 if (x>365) else x)
#availability 365 valores negativos y faltantes se reemplazan con la media:
df['availability 365'] = df['availability 365'].fillna(df['availability 365'].median())
df['availability 365'] = df['availability 365'].apply(lambda x: df['availability 365'].median() if (x<0) else x)
#Borrar los registros que no tienen valores en la columna number of reviews
df = df.dropna(subset=['number of reviews'])
#pasar review rate number a número
df['review rate number'] = df['review rate number'].astype(float)
df['review rate number'] = df['review rate number'].fillna(df['review rate number'].median())
df.reset_index(drop=True, inplace=True)
return df
dataH = preprocess(data)
dataH.shape
Luego del preprocesamiento de calidad de datos, se pasa de 102083 a 100783 registros, esto es solo se eliminaron 1.27% de los datos.
ProfileReport(dataH)
Se muestra como quedaron los datos despues de la limpieza de datos
dataH.head(20)
Dado que vamos a usar regresión logística, hay que pasar los datos categporicos a numéricos:
def enconderF(df2):
#Eliminar columna Pais e id:
df2 = df2.drop(['country', 'id'], axis=1)
#instant_bookable to num
df2['instant_bookable'] = df2['instant_bookable'].replace({
False: 0,
True: 1
})
#encoder para neighbourhood group
df2 = pd.get_dummies(df2,columns=['neighbourhood group'],drop_first = True)
#Dado que el barrio tiene muchas categorias, y que se tienen otros valores de geolocalizacion se quita esta columna
df2 = df2.drop(['neighbourhood'], axis=1)
#encoder para cancellation_policy
df2 = pd.get_dummies(df2,columns=['cancellation_policy'],drop_first = True)
#encoder para room type
df2 = pd.get_dummies(df2,columns=['room type'],drop_first = True)
df2.reset_index(drop=True, inplace=True)
return df2
dataH=enconderF(dataH)
dataH.shape
dataH.head(10)
Dividimos los datos en: datos de entrenamiento (train) y datos de evaluación (test) con una relación 80% - 20% respectivamente.
train, test = train_test_split(dataH, test_size=0.4, random_state=33)
train.head()
Para trabajar el problema de regresión, se deben pasar las columnas de categoricas a numericas, como todos los predios estan en Estados Unidos vamos a dejar de disponer de esta columna
#separando la variable objetivo:
x_train = train.drop('number of reviews',axis=1)
y_train = train['number of reviews']
#separando la variable objetivo:
x_test = test.drop('number of reviews',axis=1)
y_test = test['number of reviews']
lin_reg = LinearRegression()
lin_reg.fit(x_train, y_train)
lin_reg.intercept_, lin_reg.coef_
y_pred_train = lin_reg.predict(x_train)
y_pred_test = lin_reg.predict(x_test)
#Función para ver los resultados de las métricas de un modelo entrenamiento y validación
def metricsPrint (titulo, X_train, y_train, y_pred_train, X_val, y_val, y_pred_val):
n,p = X_train.shape
print('------------ Regresión ', titulo,' con data entrenamiento------------')
print("Residual sum of squares (MSE): %.2f" % mean_squared_error(y_train,y_pred_train))
print("R2-score: %.5f" % r2_score(y_train, y_pred_train) )
print("Adj R2-score: %.5f" % ( 1-(1-r2_score(y_train, y_pred_train))*(n-1)/(n-p-1)) )
n,p = X_val.shape
print('------------ Regresión ', titulo, ' con data test ------------')
print("Residual sum of squares (MSE): %.2f" % mean_squared_error(y_val,y_pred_val))
print("R2-score: %.5f" % r2_score(y_val, y_pred_val) )
print("Adj R2-score: %.5f" % ( 1-(1-r2_score(y_val, y_pred_val))*(n-1)/(n-p-1)) )
metricsPrint ('Regresion Lineal', x_train, y_train, y_pred_train, x_test, y_test, y_pred_test)
Con un primer modelo básico de regresión lineal, se observa que el error aumenta un poco con los datos de test, lo que hace que la métrica del R2-score disminuya ligeramente con los datos de test, y la métrica del R2-score no es un gran valor de predicción.
scaler = MinMaxScaler()
scaler.fit(x_train)
columns = x_train.columns
X_train_norm = scaler.fit_transform(x_train)
X_train_norm = pd.DataFrame(X_train_norm,columns=columns)
X_train_norm
lin_regN = LinearRegression()
lin_regN.fit(X_train_norm, y_train)
y_pred_trainN = lin_regN.predict(X_train_norm)
X_test_norm = scaler.fit_transform(x_test)
X_test_norm = pd.DataFrame(X_test_norm,columns=columns)
X_test_norm
y_pred_testN = lin_regN.predict(X_test_norm)
metricsPrint ('Regresion Lineal', X_train_norm, y_train, y_pred_trainN, X_test_norm, y_test, y_pred_testN)
Con este modelo aumenta aumenta el error en test, pero disminuye un poco el sobreajuste, respecto al primer modelo
Dada la cantidad de datos, y que la ejecución Polinomial requiere de más ram permitida por el google Colab, se aplica reducción de la dimensionalidad PCA a 10, para poder ejecutar la regresión polinomial con grado 2.
pca = PCA(n_components=10, random_state=32)
pca.fit(x_train)
x_train_PCA = pca.transform(x_train)
x_train_PCA
poly_features = PolynomialFeatures(degree=2, include_bias=False)
x_poly_train = poly_features.fit_transform(x_train_PCA)
dataset = pd.DataFrame(x_poly_train)
x_poly_train.shape
lin_regP = LinearRegression()
lin_regP.fit(x_poly_train, y_train)
y_pred_train = lin_regP.predict(x_poly_train)
x_test_PCA = pca.transform(x_test)
x_poly_test = poly_features.fit_transform(x_test_PCA)
y_pred_test = lin_regP.predict(x_poly_test)
metricsPrint ('Regresion Polinomial P=2', x_poly_train, y_train, y_pred_train, x_poly_test, y_test, y_pred_test)
Ahora se hace regresión polinomial con grado 3, y la reducción de dimensionalidad la dejaremos en 3 (buscando trabajar con una dimensión mas pequeña y evitar el error de ram)
pca = PCA(n_components=3, random_state=32)
pca.fit(x_train)
x_train_PCA = pca.transform(x_train)
x_train_PCA
poly_features = PolynomialFeatures(degree=3, include_bias=False)
x_poly_train = poly_features.fit_transform(x_train_PCA)
dataset = pd.DataFrame(x_poly_train)
x_poly_train.shape
lin_regP = LinearRegression()
lin_regP.fit(x_poly_train, y_train)
y_pred_train = lin_regP.predict(x_poly_train)
x_test_PCA = pca.transform(x_test)
x_poly_test = poly_features.fit_transform(x_test_PCA)
y_pred_test = lin_regP.predict(x_poly_test)
metricsPrint ('Regresion Polinomial P=3', x_poly_train, y_train, y_pred_train, x_poly_test, y_test, y_pred_test)
Dado que el polinomio grado 2 funcionó un poco mejor que grado 3, ahora le vamos a aplicar una regularización lasso para que si restringiendo alguna característica se tiene un mejor valor
pca = PCA(n_components=10, random_state=32)
pca.fit(x_train)
x_train_PCA = pca.transform(x_train)
x_train_PCA
poly_features = PolynomialFeatures(degree=2, include_bias=False)
x_poly_train = poly_features.fit_transform(x_train_PCA)
dataset = pd.DataFrame(x_poly_train)
x_poly_train.shape
x_test_PCA = pca.transform(x_test)
x_poly_test = poly_features.fit_transform(x_test_PCA)
Se decide un alfa de 35 como hiperparámetro de la regularización Lasso
LassoModel = Lasso(alpha=35)
LassoModel.fit(x_poly_train, y_train)
y_pred_lass_train_1 = LassoModel.predict(x_poly_train)
y_pred_lass_train_1
y_pred_lass_test_1 = LassoModel.predict(x_poly_test)
metricsPrint ('Regresion Lasso alfa = 35', x_poly_train, y_train, y_pred_lass_train_1, x_poly_test, y_test, y_pred_lass_test_1)
columnas = ['a.Reglineal', 'b.Reg linealyMinMaxScaller', 'c.RegPoli(2)_PCA=10', 'd.RegPoli(3)_PCA=3', 'e.Regpoli(2)_Lasso'] # definimos los nombres de las columnas
filas = ['MSE Train', 'MSE Test', 'R2-score Train', 'R2-score Test'] # definimos los nombres de las filas
datos = [
['2335.63', '2335.63', '2265.93', '2301.42', '2290.17'],
['2500.79', '2506.07', '2435.28', '2467.82', '2455.68' ],
['2.130%', '2.130%', '5.051%', '3.563%', '4.035%' ],
['2.100%', '1.894%', '4.665%', '3.391%', '3.866%']
]
table = pd.DataFrame(datos, columns=columnas, index=filas)
table.head(4)
De acuerdo a las comparaciones de las métricas, aunque ninguno tiene una valore de predicción aceptable, de los modelos revisados el mejor fue el c) de regresión polinomial grado 2 y PCA de 10. Por su metrica con data de evaluación del 4.665% que es la mas alta no es suficiente para el uso en la imobiliaria para ello se esperaría un modelo de almenos el 60%, y dado que es un valor muy pequeño no se considera pueda responder adecuadamente al objetivo planteado.
pca = PCA(n_components=10, random_state=32)
pca.fit(x_train)
x_train_PCA = pca.transform(x_train)
poly_features = PolynomialFeatures(degree=2, include_bias=False)
x_poly_train = poly_features.fit_transform(x_train_PCA)
dataset = pd.DataFrame(x_poly_train)
lin_regP = LinearRegression()
lin_regP.fit(x_poly_train, y_train)
x_train_PCA.shape
Se aplica las funciones de preprocesamiento a los datos de los inmuebles que están próximos a publicarse, para el caso de las columnas innexistentes se colo como numero de reviews 7 y como review rate number 3 que son los datos de la mediana vista en los historicos (esto para que las funciones no fallen, y en adición la cantidad de reviews igual luego se quita dado que es la variable a predecir)
dataP2 = dataP.copy()
dataP2['number of reviews']=7
dataP2['review rate number']=3
dataPRED = preprocess(dataP2)
dataPRED.shape
dataPRED.head(10)
dataPRED_enc=enconderF(dataPRED)
dataPRED_enc = dataPRED_enc.drop('number of reviews',axis=1)
x_PRED_PCA = pca.transform(dataPRED_enc)
x_poly_PRED = poly_features.fit_transform(x_PRED_PCA)
y_PRED = lin_regP.predict(x_poly_PRED)
y_PRED.size
dataPRED = dataPRED.drop('review rate number',axis=1)
Se adiciona la columna 'number of reviews' con los datos de la predicción del modelo:
dataPRED['number of reviews']=y_PRED
dataPRED
sns.boxplot(x=dataPRED["number of reviews"])
dataPRED["number of reviews"].describe()
Dentro de lo visto se considera no popular si su número de reviews no supera la media (en este caso 25, en el caso del histórico era del 27)
dataPRED['costo publicidad'] = dataPRED['price'].apply(lambda x: x*0.02)
dataPRED['costo publicidad'] = dataPRED["number of reviews"].apply(lambda x: 0 if (x >= 25) else x)
dataPRED